/*
 * Oware, the acient African game.
 * Copyright 2013 Przemyslaw Rzepecki
 * Contact: przemekr@sdfeu.org
 * 
 * This file is part of Oware.
 * 
 * Oware is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 * 
 * Oware is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * Oware.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <math.h>
#include <algorithm> 
#include <stack>
#include <unistd.h>
#include "agg_basics.h"
#include "agg_rendering_buffer.h"
#include "agg_rasterizer_scanline_aa.h"
#include "agg_scanline_u.h"
#include "agg_scanline_p.h"
#include "agg_renderer_scanline.h"
#include "agg_ellipse.h"
#include "agg_pixfmt_gray.h"
#include "agg_pixfmt_rgb.h"
#include "agg_pixfmt_amask_adaptor.h"
#include "agg_span_allocator.h"
#include "agg_span_gradient.h"
#include "agg_span_interpolator_linear.h"
#include "agg_conv_transform.h"
#include "agg_color_rgba.h"
#include "agg_gsv_text.h"
#include "ctrl/agg_slider_ctrl.h"
#include "agg_button_ctrl.h"
#include "platform/agg_platform_support.h"
#include <unistd.h>
#include <stdint.h>
#include "stdio.h"
#include "gomoku.h"
#include "gomoku_ai.h"

#ifdef MOBILE
#define START_H  0
#define START_W  0
#define S 10
#define CTRL_TEXT_THICKNESS 3
#define WINDOW_FLAGS agg::window_fullscreen
#else
#define START_H  500
#define START_W  380
#define S 7
#define CTRL_TEXT_THICKNESS 1
#define WINDOW_FLAGS agg::window_resize
#endif

#if __ANDROID__
#include <android/log.h>
#define DEBUG_PRINT(...) do{ __android_log_print(ANDROID_LOG_INFO, __FILE__, __VA_ARGS__ ); } while (false)
#else
#define DEBUG_PRINT(...) do{fprintf(stderr, __VA_ARGS__ ); } while (false)
#endif

enum flip_y_e { flip_y = true };
const agg::rgba transp(0, 0, 0, 0);
const agg::rgba lblue(0,0,128,128);
const agg::rgba lgray(60,60,60);
const agg::rgba green(0,120,0);
const agg::rgba yellow(63,63,0);
const agg::rgba red(0.8,0,0,0.5);
const agg::rgba black(0,0,0);

static agg::platform_support* app;

CbResult callback()
{
   return app->elapsed_time() <500.0? CONTINUE:
          app->elapsed_time() <1000.0? HURRY_UP:
          STOP_NOW;
}

class View
{
public:
   View(agg::platform_support& application): app(application) {}
   virtual void on_draw() {}
   virtual void on_resize(int sx, int sy) {}
   virtual void on_idle() {}
   virtual void on_mouse_move(int x, int y, unsigned flags) {}
   virtual void on_multi_gesture(float dTheta, float dDist, float x,
         float y, int numFinders) {}
   virtual void on_mouse_button_down(int x, int y, unsigned flags) {}
   virtual void on_mouse_button_up(int x, int y, unsigned flags) {}
   virtual void on_key(int x, int y, unsigned key, unsigned flags) {}
   virtual void on_ctrl_change() {}
   agg::ctrl_container m_ctrls;
   void add_ctrl(agg::ctrl& c) { m_ctrls.add(c); c.transform(app.trans_affine_resizing());}
protected:
   agg::platform_support& app;
};


class App: public agg::platform_support
{
public:
   App(agg::pix_format_e format, bool flip_y) :
      agg::platform_support(format, flip_y) {}
   void changeView(const char* name) {};
   View* view;

   virtual void on_ctrl_change()
   {
      view->on_ctrl_change();
   }

   virtual void on_resize(int x, int y)
   {
      view->on_resize(x, y);
   }

   virtual void on_idle()
   {
      view->on_idle();
   }

   virtual void on_mouse_button_up(int x, int y, unsigned flags)
   {
      view->on_mouse_button_up(x, y, flags);
   }

   virtual void on_mouse_button_down(int x, int y, unsigned flags)
   {
      view->on_mouse_button_down(x, y, flags);
   }

   virtual void on_mouse_move(int x, int y, unsigned flags)
   {
      view->on_mouse_move(x, y, flags);
   }

   virtual void on_multi_gesture(float x, float y,
         float dTheta, float dDist, int numFingers)
   {
      view->on_multi_gesture(x, y, dTheta, dDist, numFingers);
   }


   virtual void on_draw()
   {
      view->on_draw();
   }

};

class GameView : public View
{
public:
   GameView(App& app): View(app),
      newg(S, S,   8*S, S  +2.5*S,   "NEW",  !flip_y),
      undo(S, 5*S, 8*S, 5*S+2.5*S,   "UNDO", !flip_y),
      p1_ctr(20*S, S, 15*S+23*S,   S  +2*S,  !flip_y),
      p2_ctr(20*S, 5*S, 15*S+23*S, 5*S+2*S,  !flip_y),
      animation(50),
      gomoku()
   {
      lx = ly = 0;

      p1_ctr.range(0, 8);
      p1_ctr.num_steps(8);
      p1_ctr.value(0);
      p1_ctr.label("P1: H");
      p1_ctr.background_color(transp);
      p1_ctr.text_color(black);
      p1_ctr.text_thickness(CTRL_TEXT_THICKNESS);
      p1_ctr.border_width(0, 0);

      p2_ctr.range(0, 8);
      p2_ctr.num_steps(8);
      p2_ctr.value(0);
      p2_ctr.label("P2: H");
      p2_ctr.background_color(transp);
      p2_ctr.text_color(black);
      p2_ctr.text_thickness(CTRL_TEXT_THICKNESS);
      p2_ctr.border_width(0, 0);
      undo.background_color(red);
      newg.background_color(red);


      add_ctrl(p1_ctr);
      add_ctrl(p2_ctr);
      add_ctrl(undo);
      add_ctrl(newg);
   }

   virtual void on_ctrl_change()
   {
      app.wait_mode(true);
      if (gomoku.endOfTheGame())
      {
         animation = 0;
      }
      if (gomoku.currentPlayer() == WHITE && p1_ctr.value()
            || gomoku.currentPlayer() == BLACK && p2_ctr.value())
      {
         app.wait_mode(false);
      }

      if (undo.status())
      {
         animation = 10;
         undo.status(false);
         if (undoList.empty())
            return;
         Move m = undoList.top(); undoList.pop();
         gomoku.undo(m);
         if (gomoku.currentPlayer() == WHITE  && p1_ctr.value()
               || gomoku.currentPlayer() == BLACK  && p2_ctr.value())
         {
            if (undoList.empty())
               return;
            m = undoList.top(); undoList.pop();
            gomoku.undo(m);
         }
      }

      if (newg.status())
      {
         newg.status(false);
         animation = 10;
         Gomoku_ai new_gomoku;
         gomoku = new_gomoku;
      }
      p1_ctr.label(!p1_ctr.value()? "P1: H": "P1: C(%1.0f)");
      p2_ctr.label(!p2_ctr.value()? "P2: H": "P2: C(%1.0f)");

      app.force_redraw();
   }

   virtual void on_resize(int, int)
   {
      app.force_redraw();
      double w = app.rbuf_window().width();
      double h = app.rbuf_window().height();
      
      size = int(std::min(w*0.95, h*0.9));
      hshift = h - size;
      hshift -= hshift/4;
      wshift = w - size;
      wshift /= 2;
   }

   virtual void on_idle()
   {
      if (animation)
      {
         animation--;
         app.wait_mode(false);
         usleep(1000);
         app.force_redraw();
         return;
      }

      if (gomoku.endOfTheGame())
      {
         app.wait_mode(true);
         return;
      }

      if (gomoku.currentPlayer() == WHITE && !p1_ctr.value()
            || gomoku.currentPlayer() == BLACK && !p2_ctr.value())
      {
         app.wait_mode(true);
         return;
      }

      app.start_timer();
      Move m = ai_move(gomoku, gomoku.currentPlayer() == WHITE?
            p1_ctr.value() : p2_ctr.value(), callback);
      printf("AI move %s %d %d\n", gomoku.currentPlayer() == WHITE?
            "WHITE": "BLACK", m.first, m.second);
      gomoku.move(m.first, m.second);
      undoList.push(m);
      animation = gomoku.endOfTheGame()? 50: 20;

      app.wait_mode(false);
      app.force_redraw();
   }

   int pixToX(int x)
   {
      x -= wshift;
      if (x < 0) return -1;

      double wsize = size/(SIZE-1);
      return (x+wsize/2)/wsize;
   }
   int pixToY(int y)
   {
      y -= hshift;
      if (y < 0) return -1;

      double hsize = size/(SIZE-1);
      return (y+hsize/2)/hsize;
   }


   virtual void on_mouse_button_up(int x, int y, unsigned flags)
   {
      if (m_ctrls.on_mouse_button_up(x, y))
         return;

      try {
         gomoku.move(pixToX(x), pixToY(y));
         animation = gomoku.endOfTheGame()? 50: 20;
         undoList.push(Move(pixToX(x), pixToY(y)));
      } catch (MoveNotValid& m) {}
      app.force_redraw();
      app.wait_mode(false);
   }

   virtual void on_mouse_button_down(int x, int y, unsigned flags)
   {
      m_ctrls.on_mouse_button_down(x, y);
   }

   virtual void on_mouse_move(int x, int y, unsigned flags)
   {
      if (m_ctrls.on_mouse_move(x, y, flags))
         return;

      if (! flags & agg::mouse_left)
         return;

   }
   virtual void on_multi_gesture(float x, float y,
         float dTheta, float dDist, int numFingers)
   {
   }


   virtual void on_draw()
   {
      agg::pixfmt_bgr24 pf(app.rbuf_window());;
      agg::renderer_base<agg::pixfmt_bgr24> rbase(pf);
      agg::rasterizer_scanline_aa<> ras;
      agg::scanline_u8 sl;
      ras.reset();
      rbase.clear(lgray);

      double s = 1.0*size/(SIZE-1);

      int i;
      for (i = 0; i< SIZE; i++)
         rbase.blend_hline(wshift, s*i+hshift, size+wshift, lblue, 65);

      for (i = 0; i< SIZE; i++)
         rbase.blend_vline(s*i+wshift, hshift, size+hshift, lblue, 64);

      typedef agg::pixfmt_bgr24 pixfmt_type;
      typedef agg::renderer_base<pixfmt_type>                     renderer_base_type;
      typedef agg::renderer_scanline_aa_solid<renderer_base_type> renderer_scanline_type;
      renderer_scanline_type ren_sl(rbase);
      for (int i = 0; i < SIZE; i++)
         for (int j = 0; j < SIZE; j++)
         {
            if (gomoku.getPoint(i, j) == EMPTY)
               continue;

            ren_sl.color(gomoku.getPoint(i, j) == WHITE?
                  agg::rgba(1, 1, 1, 0.9):
                  agg::rgba(0.1, 0.1, 0.1, 0.9));
            agg::ellipse e;
            ras.reset();
            e.init(wshift+s*i, hshift+s*j, s*0.4, s*0.4, 128);
            ras.add_path(e);
            agg::render_scanlines(ras, sl, ren_sl);

         }
      if (animation && gomoku.endOfTheGame())
      {
         ren_sl.color(agg::rgba(1, 0, 0, 0.02*animation--));
         ras.reset();
         for (int i = 0; i < 5; i++)
         {
            agg::ellipse e;
            e.init(wshift+s*gomoku.winnigRow[i].first,
                   hshift+s*gomoku.winnigRow[i].second,
                   s*0.4, s*0.4, 128);
            ras.add_path(e);
         }
         agg::render_scanlines(ras, sl, ren_sl);
      } else if (animation && !undoList.empty())
      {
         ren_sl.color(agg::rgba(1, 0, 0, 0.02*animation--));
         ras.reset();
         agg::ellipse e;
         Move m = undoList.top();
         e.init(wshift+s*m.first, hshift+s*m.second, s*0.4, s*0.4, 128);
         ras.add_path(e);
         agg::render_scanlines(ras, sl, ren_sl);
      }

      double scale = app.rbuf_window().width()/400.0;
      static agg::trans_affine shape_mtx; shape_mtx.reset();
      shape_mtx *= agg::trans_affine_scaling(scale);
      shape_mtx *= agg::trans_affine_translation(0, 0);
      p1_ctr.transform(shape_mtx);
      p2_ctr.transform(shape_mtx);
      undo.transform(shape_mtx);
      newg.transform(shape_mtx);

      agg::render_ctrl(ras, sl, rbase, p1_ctr);
      agg::render_ctrl(ras, sl, rbase, p2_ctr);
      agg::render_ctrl(ras, sl, rbase, undo);
      agg::render_ctrl(ras, sl, rbase, newg);
   }
private:
    Gomoku_ai gomoku;
    std::stack<Move> undoList;
    int size, wshift, hshift;
    int lx, ly; 
    agg::slider_ctrl<agg::rgba8> p1_ctr;
    agg::slider_ctrl<agg::rgba8> p2_ctr;
    agg::button_ctrl<agg::rgba8> undo;
    agg::button_ctrl<agg::rgba8> newg;
    int animation;

    void draw_text(double x, double y, double size, const char* str, int attr)
    {
       typedef agg::pixfmt_bgr24 pixfmt_type;
       agg::rasterizer_scanline_aa<> m_ras;
       agg::scanline_p8              m_sl;
       pixfmt_type pf(app.rbuf_window());
       agg::renderer_base<pixfmt_type> rb(pf);
       agg::renderer_scanline_aa_solid<agg::renderer_base<pixfmt_type> > ren(rb);

       agg::gsv_text txt;
       agg::conv_stroke<agg::gsv_text> txt_stroke(txt);
       txt_stroke.width(size/6);
       txt_stroke.line_cap(agg::round_cap);
       txt.size(size);
       txt.start_point(x, y);
       txt.text(str);
       m_ras.add_path(txt_stroke);
       ren.color(agg::rgba(0, 0, 0.6, 0.8));
       agg::render_scanlines(m_ras, m_sl, ren);
    }
};

class the_application: public App
{
public:
   the_application(agg::pix_format_e format, bool flip_y) :
      App(format, flip_y)
   {
      gameView = new GameView(*this);
      view = gameView;
   }
private:
   GameView* gameView;
};



int agg_main(int argc, char* argv[])
{
    the_application app(agg::pix_format_bgr24, flip_y);
    app.caption("GomokuAGG");
    if (app.init(START_W, START_H, WINDOW_FLAGS))
    {
        return app.run();
    }
    return 1;
}
